package mysql;

import com.goldhuman.Common.Marshal.MarshalException;
import com.goldhuman.Common.Marshal.OctetsStream;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import xdb.Trace;
import xdb.Xdb;

import java.sql.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

public class Mysql4Merge {
    private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";

    private HikariDataSource readConn = null;
    private HikariDataSource writeConn = null;
    private MySQLConf mySQLConf = null;

    public Mysql4Merge(MySQLConf mySQLConf) {
        try {
            this.mySQLConf = mySQLConf;
            if (xdb.Xdb.isDBServer() == false) {
                return;
            }
            readConn = new HikariDataSource(InitHikariConfig(true, mySQLConf.getMinPoolSize(), mySQLConf.getMaxPoolSize()));
            writeConn = new HikariDataSource(InitHikariConfig(false, mySQLConf.getMinPoolSize(), mySQLConf.getMaxPoolSize()));
            if (readConn == null || writeConn == null) {
                Trace.error("MySQL Connection Pool Init Error, System Exit!");
                Xdb.getInstance().stop();
                Runtime.getRuntime().halt(0);
            } else {
                Trace.info("MySQL Connection Pool Init OK!");
            }
        } catch (Exception e) {
            xdb.Trace.error("MySQL Connection Init Error!", e);
            Xdb.getInstance().stop();
            Runtime.getRuntime().halt(0);
        }
    }

    public static void CloseConn(Connection conn) throws SQLException {
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                xdb.Trace.error("read conn close", e);
            }
        }
    }

    private HikariConfig InitHikariConfig(boolean autoCommit, int minPoolSize, int maxPoolSize) {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName(DRIVER_CLASS);
        hikariConfig.setJdbcUrl("jdbc:mysql://" + mySQLConf.getIp() + ":" + mySQLConf.getPort() + "/" + mySQLConf.getDbName()
                + "?useUnicode=true&characterEncoding=utf8&useSSL=false&rewriteBatchedStatements=true");
        hikariConfig.setUsername(mySQLConf.getUser());
        hikariConfig.setPassword(mySQLConf.getPassword());
        hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
        hikariConfig.addDataSourceProperty("prepStmtCacheSize", 512);
        hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
        hikariConfig.setConnectionTimeout(1000); // 1 Second
        hikariConfig.setConnectionTestQuery("SELECT 1");
        hikariConfig.setAutoCommit(autoCommit);
        hikariConfig.setMinimumIdle(minPoolSize);
        hikariConfig.setMaximumPoolSize(maxPoolSize);
        return hikariConfig;
    }

    public String GetDBName() {
        return mySQLConf.getDbName();
    }

    public void Exit() {
        try {
            if (readConn != null) {
                readConn.close();
                Trace.info("Read Conn Close OK!");
            }
            if (writeConn != null) {
                writeConn.close();
                Trace.info("Write Conn Close OK!");
            }
        } catch (Exception e) {
            xdb.Trace.error("conn close Error!", e);
        }
    }

    public Connection GetReadConn() throws SQLException {
        try {
            return readConn.getConnection();
        } catch (Exception e) {
            xdb.Trace.error("read conn get Error!", e);
            //readConn.resumePool();
            throw new SQLException();
        }
    }

    public Connection GetWriteConn() throws SQLException {
        try {
            return writeConn.getConnection();
        } catch (Exception e) {
            xdb.Trace.error("write conn get Error!", e);
            //writeConn.resumePool();
            throw new SQLException();
        }
    }

    public Set<String> GetTableNames() {
        try {
            String sql = "select table_name from information_schema.tables where table_schema='" + mySQLConf.getDbName() + "'";
            Set<String> tableNames = new HashSet<String>();
            Connection conn = GetReadConn();
            PreparedStatement pst = conn.prepareStatement(sql);
            ResultSet ret = pst.executeQuery();
            while (ret.next()) {
                tableNames.add(ret.getString(1));
            }
            pst.close();
            conn.close();
            return tableNames;
        } catch (Exception e) {
            xdb.Trace.error("GetTableNames", e);
            return new HashSet<String>();
        }
    }

    public OctetsStream ExecQuery(String sql, Connection conn) throws SQLException {
        PreparedStatement pst = null;
        try {
            OctetsStream octStream = null;
            pst = conn.prepareStatement(sql);
            ResultSet ret = pst.executeQuery();
            if (ret.next()) {
                String value = ret.getString(1);
                octStream = new OctetsStream().marshalJson(value);
            }
            return octStream;
        } catch (Exception e) {
            xdb.Trace.error("SQL is " + sql, e);
            throw new SQLException();
        } finally {
            if (pst != null) {
                try {
                    pst.close();
                } catch (SQLException e) {
                    xdb.Trace.error("read pst close", e);
                }
            }
        }
    }

    public OctetsStream ExecQuery(String sql) throws SQLException {
        Connection conn = null;
        try {
            conn = GetReadConn();
            return ExecQuery(sql, conn);
        } catch (Exception e) {
            xdb.Trace.error("SQL is " + sql, e);
            throw new SQLException();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    xdb.Trace.error("read conn close", e);
                }
            }
        }
    }

    /**
     * 查询表的全部key值；用于判断表是否key冲突
     *
     * @param tableName
     * @return
     * @throws SQLException
     */
    public ArrayList<String> QueryKeys(String tableName) throws SQLException {
        Connection conn = null;
        try {
            conn = GetReadConn();
            return QueryKeys(tableName, conn);
        } catch (Exception e) {
            xdb.Trace.error("QueryKeys error: table = " + tableName, e);
            throw new SQLException();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    xdb.Trace.error("read conn close", e);
                }
            }
        }
    }

    /**
     * 查询表的全部key值；用于判断表是否key冲突
     *
     * @param tableName
     * @return
     * @throws SQLException
     */
    public ArrayList<String> QueryKeys(String tableName, Connection conn) throws SQLException {
        PreparedStatement pst = null;
        ArrayList<String> resultList = new ArrayList<String>();
        try {
            pst = conn.prepareStatement("select k from " + tableName);
            ResultSet ret = pst.executeQuery();
            while (ret.next()) {
                resultList.add(ret.getString(1));
            }
        } catch (Exception e) {
            xdb.Trace.error("QueryKeys error: table = " + tableName, e);
            throw new SQLException();
        } finally {
            if (pst != null) {
                try {
                    pst.close();
                } catch (SQLException e) {
                    xdb.Trace.error("read pst close", e);
                }
            }
        }
        return resultList;
    }

    public ArrayList<String> queryList(String sql) throws SQLException {
        Connection conn = null;
        try {
            conn = GetReadConn();
            return queryList(sql, conn);
        } catch (Exception e) {
            xdb.Trace.error("QueryKeys error: sql = " + sql, e);
            throw new SQLException();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    xdb.Trace.error("read conn close", e);
                }
            }
        }
    }

    public ArrayList<String> queryList(String sql, Connection conn) throws SQLException {
        PreparedStatement pst = null;
        ArrayList<String> resultList = new ArrayList<String>();
        try {
            pst = conn.prepareStatement(sql);
            ResultSet ret = pst.executeQuery();
            while (ret.next()) {
                resultList.add(ret.getString(1));
            }
        } catch (Exception e) {
            xdb.Trace.error("QueryKeys error: sql = " + sql, e);
            throw new SQLException();
        } finally {
            if (pst != null) {
                try {
                    pst.close();
                } catch (SQLException e) {
                    xdb.Trace.error("read pst close", e);
                }
            }
        }
        return resultList;
    }

    public int ExecUpdate(String sql, OctetsStream value) {
        if (!this.mySQLConf.isbAutoSql() && sql.startsWith("CREATE TABLE")) {
            Trace.info(sql + ";");
            return 0;
        }
        Connection conn = null;
        try {
            conn = GetWriteConn();
            PreparedStatement pst = conn.prepareStatement(sql);
            if (value != null) {
                pst.setString(1, value.unmarshalJson());
            }
            int ret = pst.executeUpdate();
            conn.commit();
            pst.close();
            return ret;
        } catch (SQLIntegrityConstraintViolationException duplicateEx) {
            xdb.Trace.error("SQL is " + sql, duplicateEx);
            if (value != null) {
                try {
                    xdb.Trace.error("Json is ---" + value.unmarshalJson() + "---");
                } catch (MarshalException e1) {
                    xdb.Trace.error("MarshalException", e1);
                }
            }
            return -1;
        } catch (Exception e) {
            xdb.Trace.error("SQL is " + sql, e);
            if (value != null) {
                try {
                    xdb.Trace.error("Json is ---" + value.unmarshalJson() + "---");
                } catch (MarshalException e1) {
                    xdb.Trace.error("MarshalException", e1);
                }
            }
            return -1;
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    xdb.Trace.error("write conn close", e);
                }
            }
        }
    }
}
